/*
 * Copyright (c) 2020, yifengling0. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. No personal names or organizations' names associated with the
 *    Atomthreads project may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "i2cbit.h"
#include "debug.h"
#include "syslog.h"


#define SET_SDA(ops, val)   ops->set_sda(ops->data, val)
#define SET_SCL(ops, val)   ops->set_scl(ops->data, val)
#define GET_SDA(ops)        ops->get_sda(ops->data)
#define GET_SCL(ops)        ops->get_scl(ops->data)

static void i2c_delay(I2cBitOps* ops)
{
    ops->udelay((ops->delay_us + 1) >> 1);
}

static void i2c_delay2(I2cBitOps* ops)
{
    ops->udelay(ops->delay_us);
}

#define SDA_L(ops)          SET_SDA(ops, 0)
#define SDA_H(ops)          SET_SDA(ops, 1)
#define SCL_L(ops)          SET_SCL(ops, 0)

/**
 * release scl line, and wait scl line to high.
 */
static int SCL_H(I2cBitOps* ops)
{
    uint32_t start;

    SET_SCL(ops, 1);

    start = atomTimeGet();
    while (!GET_SCL(ops))
    {
        if ((atomTimeGet() - start) > ops->timeout)
            return -ATOM_ERROR;
        atomTimerDelay((ops->timeout + 1) >> 1);
    }

    i2c_delay(ops);

    return OK;
}

static void i2c_start(I2cBitOps* ops)
{
    SDA_L(ops);
    i2c_delay(ops);
    SCL_L(ops);
}

static void i2c_restart(I2cBitOps* ops)
{
    SDA_H(ops);
    SCL_H(ops);
    i2c_delay(ops);
    SDA_L(ops);
    i2c_delay(ops);
    SCL_L(ops);
}

static void i2c_stop(I2cBitOps* ops)
{
    SDA_L(ops);
    i2c_delay(ops);
    SCL_H(ops);
    i2c_delay(ops);
    SDA_H(ops);
    i2c_delay2(ops);
}

static int i2c_waitack(I2cBitOps* ops)
{
    int ack;

    SDA_H(ops);
    i2c_delay(ops);

    if (SCL_H(ops) < 0)
    {
        SYSLOG(LOG_WARNING, "wait ack timeout");

        return NG;
    }

    ack = !GET_SDA(ops);    /* ACK : SDA pin is pulled low */

    SCL_L(ops);

    return ack;
}

static int32_t i2c_writeb(struct I2c* bus, uint8_t data)
{
    int32_t i;
    uint8_t bit;

    I2cBitOps* ops = (I2cBitOps*)bus->Protected.priv;

    for (i = 7; i >= 0; i--)
    {
        SCL_L(ops);
        bit = (data >> i) & 1;
        SET_SDA(ops, bit);
        i2c_delay(ops);
        if (SCL_H(ops) < 0)
        {
            SYSLOG(LOG_DEBUG, "i2c_writeb: 0x%02x, " "wait scl pin high timeout at bit %d", data, i);
            return NG;
        }
    }
    SCL_L(ops);
    i2c_delay(ops);

    return i2c_waitack(ops);
}

static int32_t i2c_readb(I2c* bus)
{
    uint8_t i;
    uint8_t data = 0;
    I2cBitOps* ops = (I2cBitOps*)bus->Protected.priv;

    SDA_H(ops);
    i2c_delay(ops);
    for (i = 0; i < 8; i++)
    {
        data <<= 1;

        if (SCL_H(ops) < 0)
        {
            SYSLOG(LOG_DEBUG, "i2c_readb: wait scl pin high " "timeout at bit %d", 7 - i);
            return NG;
        }

        if (GET_SDA(ops))
            data |= 1;
        SCL_L(ops);
        i2c_delay2(ops);
    }

    return data;
}

static size_t i2c_send_bytes(I2c* bus,
    struct i2c_msg* msg)
{
    int32_t ret;
    size_t bytes = 0;
    const uint8_t* ptr = msg->buf;
    int32_t count = msg->len;
    uint16_t ignore_nack = msg->flags & I2C_IGNORE_NACK;

    while (count > 0)
    {
        ret = i2c_writeb(bus, *ptr);

        if ((ret > 0) || (ignore_nack && (ret == 0)))
        {
            count--;
            ptr++;
            bytes++;
        }
        else if (ret == 0)
        {
            SYSLOG(LOG_ERR, "send bytes: NACK.");

            return 0;
        }
        else
        {
            SYSLOG(LOG_ERR, "send bytes: error %d", ret);

            return ret;
        }
    }

    return bytes;
}

static int i2c_send_ack_or_nack(I2c* bus, int ack)
{
    I2cBitOps* ops = (I2cBitOps*)bus->Protected.priv;

    if (ack)
        SET_SDA(ops, 0);
    i2c_delay(ops);
    if (SCL_H(ops) < 0)
    {
        SYSLOG(LOG_ERR, "ACK or NACK timeout.");

        return NG;
    }
    SCL_L(ops);

    return OK;
}

static size_t i2c_recv_bytes(I2c* bus, struct i2c_msg* msg)
{
    int32_t val;
    int32_t bytes = 0;   /* actual bytes */
    uint8_t* ptr = msg->buf;
    int32_t count = msg->len;
    const uint32_t flags = msg->flags;

    while (count > 0)
    {
        val = i2c_readb(bus);
        if (val >= 0)
        {
            *ptr = val;
            bytes++;
        }
        else
        {
            break;
        }

        ptr++;
        count--;

        SYSLOG(LOG_DEBUG, "recieve bytes: 0x%02x, %s", val, (flags & I2C_NO_READ_ACK) ? "(No ACK/NACK)" : (count ? "ACK" : "NACK"));

        if (!(flags & I2C_NO_READ_ACK))
        {
            val = i2c_send_ack_or_nack(bus, count);
            if (val < 0)
                return val;
        }
    }

    return bytes;
}

static int32_t i2c_send_address(I2c *bus,
    uint8_t                addr,
    int32_t                retries)
{
    I2cBitOps* ops = (I2cBitOps*)bus->Protected.priv;
    int32_t i;
    int32_t ret = 0;

    for (i = 0; i <= retries; i++)
    {
        ret = i2c_writeb(bus, addr);
        if (ret == 1 || i == retries)
            break;
        SYSLOG(LOG_DEBUG, "send stop condition");
        i2c_stop(ops);
        i2c_delay2(ops);
        SYSLOG(LOG_DEBUG, "send start condition");
        i2c_start(ops);
    }

    return ret;
}

static int i2c_bit_send_address(I2c* bus,
    struct i2c_msg* msg)
{
    uint16_t flags = msg->flags;
    uint16_t ignore_nack = msg->flags & I2C_IGNORE_NACK;
    I2cBitOps* ops = (I2cBitOps*)bus->Protected.priv;

    uint8_t addr1, addr2;
    int32_t retries;
    int ret;

    retries = ignore_nack ? 0 : bus->Protected.retries;

    if (flags & I2C_ADDR_10BIT)
    {
        addr1 = 0xf0 | ((msg->addr >> 7) & 0x06);
        addr2 = msg->addr & 0xff;

        SYSLOG(LOG_DEBUG, "addr1: %d, addr2: %d", addr1, addr2);

        ret = i2c_send_address(bus, addr1, retries);
        if ((ret != 1) && !ignore_nack)
        {
            SYSLOG(LOG_WARNING, "NACK: sending first addr");

            return NG;
        }

        ret = i2c_writeb(bus, addr2);
        if ((ret != 1) && !ignore_nack)
        {
             SYSLOG(LOG_WARNING, "NACK: sending second addr");

            return NG;
        }
        if (flags & I2C_RD)
        {
            SYSLOG(LOG_DEBUG, "send repeated start condition");
            i2c_restart(ops);
            addr1 |= 0x01;
            ret = i2c_send_address(bus, addr1, retries);
            if ((ret != 1) && !ignore_nack)
            {
                SYSLOG(LOG_ERR, "NACK: sending repeated addr");

                return NG;
            }
        }
    }
    else
    {
        /* 7-bit addr */
        addr1 = msg->addr << 1;
        if (flags & I2C_RD)
            addr1 |= 1;
        ret = i2c_send_address(bus, addr1, retries);
        if ((ret != 1) && !ignore_nack)
            return NG;
    }

    return OK;
}

static size_t i2c_bit_xfer(I2c* bus, struct i2c_msg msgs[], uint32_t num)
{
    struct i2c_msg* msg;
    I2cBitOps* ops = (I2cBitOps*)bus->Protected.priv;
    int32_t i, ret;
    uint16_t ignore_nack;

    SYSLOG(LOG_DEBUG, "send start condition");
    i2c_start(ops);
    for (i = 0; i < num; i++)
    {
        msg = &msgs[i];
        ignore_nack = msg->flags & I2C_IGNORE_NACK;
        if (!(msg->flags & I2C_NO_START))
        {
            if (i)
            {
                i2c_restart(ops);
            }
            ret = i2c_bit_send_address(bus, msg);
            if ((ret != OK) && !ignore_nack)
            {
                SYSLOG(LOG_DEBUG, "receive NACK from device addr 0x%02x msg %d",
                    msgs[i].addr, i);
                goto out;
            }
        }
        if (msg->flags & I2C_RD)
        {
            ret = i2c_recv_bytes(bus, msg);
            if (ret >= 1)
                SYSLOG(LOG_DEBUG, "read %d byte%s", ret, ret == 1 ? "" : "s");
            if (ret < msg->len)
            {
                if (ret >= 0)
                    ret = NG;
                goto out;
            }
        }
        else
        {
            ret = i2c_send_bytes(bus, msg);
            if (ret >= 1)
                SYSLOG(LOG_DEBUG, "write %d byte%s", ret, ret == 1 ? "" : "s");
            if (ret < msg->len)
            {
                if (ret >= 0)
                    ret = NG;
                goto out;
            }
        }
    }
    ret = i;

out:
    SYSLOG(LOG_DEBUG, "send stop condition");
    i2c_stop(ops);

    return ret;
}

static I2cOps i2c_bit_bus_ops =
{
    i2c_bit_xfer,
    NULL,
};

int I2cBitCreate(I2c* bus, const char* name, I2cBitOps *ops)
{
    bus->Protected.ops = &i2c_bit_bus_ops;
    bus->Protected.priv = (void*)ops;
    I2cCreate(bus, name);

    return ATOM_OK;
}
